package com.ezandroid.library.network.download;

import android.app.Application;
import android.content.Intent;

import com.ezandroid.library.network.util.NetworkUtil;
import com.ezandroid.library.network.util.SDCardUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 状态转换逻辑:
 * 等待状态 -> 正在下载
 * 暂停状态 -> 正在下载
 * 正在下载 -> 等待状态
 * 正在下载 -> 暂停状态
 * 正在下载 -> 下载结束
 */
public class ObjectsDownloader {

    private static final String TAG = "ObjectsDownloader";

    public static final String ACTION_DOWN_OBJECT_STATE_CHANGED = "action_down_object_state_changed";
    public static final String ACTION_DOWN_OBJECT_PROGRESS_UPDATED = "action_down_object_progress_updated";
    public static final String EXTRA_OBJECT = "extra_object";

    /**
     * sd卡最小剩余空间
     */
    private static final long MIN_RESERVE_SPACE = 1024 * 1024; // 1MB

    /**
     * 同时下载的最大Object数
     */
    private static final int RUNNING_MAX = 1;

    /**
     * Object下载任务列表
     */
    private CopyOnWriteArrayList<DownObjectJob> mJobs = new CopyOnWriteArrayList<DownObjectJob>();

    /**
     * 是否监听下载进度改变事件
     */
    private boolean mListenProgressChanged;

    /**
     * 非wifi下下载
     */
    private boolean mNoWifiDown;

    /**
     * Object下载完成监听器
     */
    private IDownFileFinishListener mDownFinishListener = new IDownFileFinishListener() {

        @Override
        public void onDownFileFinished(DownFileResult downTaskResult) {
            if (downTaskResult != null) {
                DownFileTask task = downTaskResult.getTask();

                // 1，找到Task对应的文件下载任务Job
                DownObjectJob job = getDownObjectJob(task);

                // 2，判断Job对应的文件下载成功还是失败
                if (job != null) {
                    IDownloadable downloadable = job.getDownloadable();
                    // 下载成功才从队列里删除，没有成功则待在队列里等待下一次唤醒
                    if (downTaskResult.getCode() == DownFileTask.CODE_SUCCESS_KEY
                            && task.getTempFileSize() == task.getFileSize()
                            && task.getFileSize() > 0) {
                        mJobs.remove(job);
                        downloadable.saveTmp2Cache();

                        jobFinish(job);
                    } else {
                        jobFail(job);
                    }
                    DownObjectJob.IDownObjectFinishListener listener = job.getDownObjectFinishListener();
                    if (listener != null) {
                        listener.onDownObjectFinished(downloadable, job.getState());
                    }
                    notifyDownObjectStateChanged(downloadable);
                }
            }
        }
    };

    /**
     * Object下载进度监听器
     */
    private IDownFileProgressListener mDownFileProgressListener = new IDownFileProgressListener() {

        @Override
        public void onDownFileProgressChanged(DownFileTask task) {
            if (mListenProgressChanged) {
                DownObjectJob job = getDownObjectJob(task);

                if (job != null) {
                    IDownloadable downloadable = job.getDownloadable();

                    notifyDownObjectProgressUpdated(downloadable);
                }
            }
        }
    };

    /**
     * 强制使用Application，避免使用Activity造成内存泄露
     */
    private static Application mApplication;

    public ObjectsDownloader(Application application) {
        mApplication = application;
    }

    private void initJob(DownObjectJob job) {
        IDownloadable downloadable = job.getDownloadable();
        if (!canDownObject(downloadable)) {
            jobFinish(job);
        } else if (isDownloading(downloadable)) {
            // Do nothing
        } else {
            startJob(job);
        }
    }

    private void startJob(DownObjectJob job) {
        if (NetworkUtil.isWifiConnected(mApplication) || (mNoWifiDown && NetworkUtil.isConnected(mApplication))) {
            int sdFlag = SDCardUtil.checkExternalSpace(MIN_RESERVE_SPACE);
            if (sdFlag == SDCardUtil.SD_OK) {
                if (getDownloadingCount() < RUNNING_MAX) {
                    downJob(job);
                } else {
                    waitJob(job);
                }
            } else {
                waitJob(job);
            }
        } else {
            cancelJob(job);
        }
    }

    private void downJob(DownObjectJob job) {
        IDownloadable downloadable = job.getDownloadable();
        job.setState(DownObjectJob.STATE_RUNNING);
        DownFileTask task = job.getTask();
        if (task == null) {
            DownFileTask downFileTask = new DownFileTask(mApplication, downloadable.getDownloadUrl(),
                    downloadable.getTmpPath());
            downFileTask.setDownFileFinishListener(mDownFinishListener);
            if (mListenProgressChanged) {
                downFileTask.addDownFileProgressListener(mDownFileProgressListener);
            }
            downFileTask.execute();
            job.setTask(downFileTask);
        }
    }

    private void waitJob(DownObjectJob job) {
        job.setState(DownObjectJob.STATE_WAITING);
    }

    private void cancelJob(DownObjectJob job) {
        DownFileTask task = job.getTask();
        if (task != null) {
            task.cancel(true);
            job.setTask(null);
        }
        waitJob(job);
    }

    protected void jobFinish(DownObjectJob job) {
        job.setState(DownObjectJob.STATE_FINISHED);
        notifyJobFinished();
    }

    protected void jobFail(DownObjectJob job) {
        job.setState(DownObjectJob.STATE_FAILED);
        notifyJobFinished();
    }

    /**
     * 开始所有任务
     */
    public void startAllJobs() {
        if (!mJobs.isEmpty()) {
            for (DownObjectJob job : mJobs) {
                initJob(job);
            }
            notifyDownObjectStateChanged(mJobs.get(0).getDownloadable());
        }
    }

    /**
     * 停止所有任务
     */
    public void pauseAllJobs() {
        if (!mJobs.isEmpty()) {
            for (DownObjectJob job : mJobs) {
                cancelJob(job);
            }
            notifyDownObjectStateChanged(mJobs.get(0).getDownloadable());
        }
    }

    /**
     * 移除所有任务
     */
    public void removeAllJobs() {
        while (!mJobs.isEmpty()) {
            DownObjectJob job = mJobs.remove(0);
            cancelJob(job);
        }
    }

    /**
     * 是否能下载该Object
     *
     * @param chessManual
     * @return
     */
    public boolean canDownObject(IDownloadable chessManual) {
        return chessManual != null && !chessManual.haveCache();
    }

    private void pauseJob(DownObjectJob job) {
        DownFileTask task = job.getTask();
        if (task != null) {
            task.cancel(true);
            job.setTask(null);
        }
        job.setState(DownObjectJob.STATE_PAUSE);
    }

    /**
     * 暂停传入的Object，下载等待的Object
     *
     * @param downloadable
     */
    public DownObjectJob pauseObject(IDownloadable downloadable) {
        DownObjectJob job = getDownObjectJob(downloadable);
        if (job != null) {
            // 正在下载 -> 暂停状态
            pauseJob(job);

            notifyJobFinished(downloadable);
            notifyDownObjectStateChanged(downloadable);
        } else {
            job = new DownObjectJob(downloadable);
        }
        return job;
    }

    public DownObjectJob downObjectImmediately(IDownloadable downloadable) {
        return downObjectImmediately(downloadable, null);
    }

    /**
     * 立即下载传入的Object
     *
     * @param downloadable
     */
    public DownObjectJob downObjectImmediately(IDownloadable downloadable, DownObjectJob.IDownObjectFinishListener listener) {
        if (!canDownObject(downloadable)) {
            DownObjectJob downObjectJob = new DownObjectJob(downloadable);
            downObjectJob.setDownObjectFinishListener(listener);
            return downObjectJob;
        }

        // 如果下载数超出最大同时下载数，则取消第一个下载的任务
        if (getDownloadingCount() >= RUNNING_MAX) {
            // 正在下载 -> 等待状态
            cancelJob(getFirstDownloadingJob());
        }

        // 等待状态 -> 正在下载 或者 暂停状态 -> 正在下载
        DownObjectJob downObjectJob = downObjectJob(downloadable, false);
        downObjectJob.setDownObjectFinishListener(listener);
        notifyDownObjectStateChanged(downloadable);
        return downObjectJob;
    }

    private DownObjectJob downObjectJob(IDownloadable downloadable, boolean first) {
        DownObjectJob job = new DownObjectJob(downloadable);
        int index = mJobs.indexOf(job);
        if (index == -1) {
            if (first) {
                mJobs.add(0, job);
            } else {
                mJobs.add(job);
            }
        } else {
            job = mJobs.get(index);
        }
        initJob(job);
        return job;
    }

    public DownObjectJob downObject(IDownloadable downloadable, boolean first, boolean notify) {
        if (!canDownObject(downloadable)) {
            return new DownObjectJob(downloadable);
        }
        DownObjectJob job = downObjectJob(downloadable, first);
        if (notify) {
            notifyDownObjectStateChanged(downloadable);
        }
        return job;
    }

    public ArrayList<DownObjectJob> downObjects(List<IDownloadable> downloadables) {
        ArrayList<DownObjectJob> downManualJobs = new ArrayList<DownObjectJob>();
        if (downloadables != null && !downloadables.isEmpty()) {
            for (IDownloadable downloadable : downloadables) {
                if (!canDownObject(downloadable)) {
                    downManualJobs.add(new DownObjectJob(downloadable));
                } else {
                    downManualJobs.add(downObjectJob(downloadable, false));
                }
            }
            notifyDownObjectStateChanged(downloadables.get(0));
        }
        return downManualJobs;
    }

    public boolean cancelObject(IDownloadable downloadable) {
        DownObjectJob job = getDownObjectJob(downloadable);
        if (job != null) {
            cancelJob(job);
            mJobs.remove(job);
            notifyJobFinished();
            notifyDownObjectStateChanged(downloadable);
            return true;
        }
        return false;
    }

    public boolean cancelObjects(List<IDownloadable> downloadables) {
        if (downloadables != null && !downloadables.isEmpty()) {
            for (IDownloadable downloadable : downloadables) {
                DownObjectJob job = getDownObjectJob(downloadable);
                if (job != null) {
                    cancelJob(job);
                    mJobs.remove(job);
                }
            }
            notifyJobFinished();
            notifyDownObjectStateChanged(downloadables.get(0));
            return true;
        }
        return false;
    }

    private void notifyJobFinished(IDownloadable excludeDownloadable) {
        if (getDownloadingCount() >= RUNNING_MAX) {
            return;
        }
        DownObjectJob job = getFirstWaitingJob(excludeDownloadable);
        if (job != null) {
            initJob(job);
        }
    }

    /**
     * 通知任务下载完成，启动新任务
     */
    private void notifyJobFinished() {
        notifyJobFinished(null);
    }

    private DownObjectJob getFirstDownloadingJob() {
        DownObjectJob j = null;
        for (int i = 0; i < mJobs.size(); i++) {
            DownObjectJob job = mJobs.get(i);
            DownFileTask downFileTask = job.getTask();
            if (downFileTask != null && !downFileTask.isCancelled()) {
                j = job;
                break;
            }
        }
        return j;
    }

    private DownObjectJob getFirstWaitingJob(IDownloadable excludeDownloadable) {
        DownObjectJob j = null;
        for (int i = 0; i < mJobs.size(); ) {
            DownObjectJob job = mJobs.get(i);
            if (job.getState() == DownObjectJob.STATE_WAITING) {
                IDownloadable downloadable = job.getDownloadable();
                if (!canDownObject(downloadable)) {
                    mJobs.remove(i);
                } else {
                    if (excludeDownloadable != null && excludeDownloadable.equals(downloadable)) {
                        i++;
                    } else {
                        j = job;
                        break;
                    }
                }
            } else {
                i++;
            }
        }
        return j;
    }

    /**
     * 是否指定Object在下载队列里
     *
     * @param downloadable
     * @return
     */
    public boolean hasObject(IDownloadable downloadable) {
        return getDownObjectJob(downloadable) != null;
    }

    /**
     * 是否正在下载指定的Object
     *
     * @param downloadable
     * @return
     */
    public boolean isDownloading(IDownloadable downloadable) {
        if (!canDownObject(downloadable)) {
            return false;
        }
        for (DownObjectJob job : mJobs) {
            if (downloadable.equals(job.getDownloadable())) {
                DownFileTask task = job.getTask();
                return task != null && !task.isCancelled();
            }
        }
        return false;
    }

    /**
     * 是否有Object正在下载
     *
     * @return
     */
    public boolean isDownloading() {
        return getDownloadingCount() > 0;
    }

    public int getDownloadPercent(IDownloadable downloadable) {
        if (!canDownObject(downloadable)) {
            return 100;
        }

        for (DownObjectJob job : mJobs) {
            if (downloadable.equals(job.getDownloadable())) {
                DownFileTask task = job.getTask();
                if (task != null) {
                    return task.getProgress();
                } else {
                    return 0;
                }
            }
        }
        return 0;
    }

    private DownObjectJob getDownObjectJob(DownFileTask task) {
        DownObjectJob job = null;
        if (task == null) {
            return job;
        }
        for (DownObjectJob j : mJobs) {
            if (task.equals(j.getTask())) {
                job = j;
                break;
            }
        }
        return job;
    }

    public DownObjectJob getDownObjectJob(IDownloadable downloadable) {
        DownObjectJob job = null;
        if (downloadable == null) {
            return job;
        }
        for (DownObjectJob j : mJobs) {
            if (downloadable.equals(j.getDownloadable())) {
                job = j;
                break;
            }
        }
        return job;
    }

    /**
     * 获取正在下载的Object的数目
     *
     * @return
     */
    private int getDownloadingCount() {
        int count = 0;
        for (DownObjectJob j : mJobs) {
            DownFileTask task = j.getTask();
            if (task != null && !task.isCancelled()) {
                count++;
            }
        }
        return count;
    }

    private static void notifyDownObjectStateChanged(IDownloadable downloadable) {
        Intent intent = new Intent(ACTION_DOWN_OBJECT_STATE_CHANGED);
        intent.putExtra(EXTRA_OBJECT, downloadable);
        mApplication.sendBroadcast(intent);
    }

    private static void notifyDownObjectProgressUpdated(IDownloadable downloadable) {
        Intent intent = new Intent(ACTION_DOWN_OBJECT_PROGRESS_UPDATED);
        intent.putExtra(EXTRA_OBJECT, downloadable);
        mApplication.sendBroadcast(intent);
    }

    public boolean isListenProgressChanged() {
        return mListenProgressChanged;
    }

    public void setListenProgressChanged(boolean mListenProgressChanged) {
        this.mListenProgressChanged = mListenProgressChanged;
    }

    public boolean isNoWifiDown() {
        return mNoWifiDown;
    }

    public void setNoWifiDown(boolean noWifiDown) {
        this.mNoWifiDown = noWifiDown;
    }
}
